﻿#region COPYRIGHT
//--------------------------------------------------------------------------------
// <copyright file="Voronoi.cs" company="starLiGHT Entertainment Studios">
//    Copyright (c) 2007, 2008, 2009, 2010, 2011
//       Roland Rosenkranz (Glatzemann@email.de)
//
//    Based on libnoise by Jason Bevins
//      Copyright (C) 2003, 2004 Jason Bevins (licensed under LGPL)
// </copyright>
// <license>
//   This file is part of starLiGHT.Noise.
//
//   starLiGHT.Noise is free software: you can redistribute it and/or modify
//   it under the terms of the GNU Lesser General Public License as published by
//   the Free Software Foundation, either version 3 of the License, or
//   (at your option) any later version.
//
//   starLiGHT.Noise is distributed in the hope that it will be useful,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   GNU Lesser General Public License for more details.
//
//   You should have received a copy of the GNU Lesser General Public License
//   along with starLiGHT.Noise. If not, see http://www.gnu.org/licenses/.
//    
//   ADDITIONAL (commercial) LICENSES for starLiGHT.Noise are available on request.
// </license>
// <version>
// **************[ starLiGHT.Engine SVN ]**********************
// * $Rev:: 10370                 $:  Revision of last commit *
// * $Author:: unknown            $:  Author of last commit   *
// * $Date:: 2011-06-12 12:29:10 #$:  Date of last commit     *
// ************************************************************
// </version>
//--------------------------------------------------------------------------------
#endregion

namespace starLiGHT.Noise.Noise
{
    #region Using Statements
    using System;
    using System.Collections.Generic;
    using System.Text;

    #endregion

    public class Voronoi : Module
    {
        /// Default displacement to apply to each cell for the
        /// noise::module::Voronoi noise module.
        public const double DEFAULT_VORONOI_DISPLACEMENT = 1.0;

        /// Default frequency of the seed points for the noise::module::Voronoi
        /// noise module.
        public const double DEFAULT_VORONOI_FREQUENCY = 1.0;

        /// Default seed of the noise function for the noise::module::Voronoi
        /// noise module.
        public const int DEFAULT_VORONOI_SEED = 0;

        protected double displacement;
        protected bool enableDistance;
        protected double frequency;
        protected int seed;

        private List<Parameter> parameterList;

        public Voronoi()
            : base(0)
        {
            this.displacement = DEFAULT_VORONOI_DISPLACEMENT;
            this.enableDistance = false;
            this.frequency = DEFAULT_VORONOI_FREQUENCY;
            this.seed = DEFAULT_VORONOI_SEED;
        }

        public override List<Parameter> ParameterList
        {
            get
            {
                if (this.parameterList == null)
                {
                    this.parameterList = new List<Parameter>();
                    this.parameterList.Add(new Parameter("Seed", "int", DEFAULT_VORONOI_SEED, int.MinValue, int.MaxValue));
                    this.parameterList.Add(new Parameter("Displacement", "double", DEFAULT_VORONOI_DISPLACEMENT, 0.0, 10.0));
                    this.parameterList.Add(new Parameter("Frequency", "double", DEFAULT_VORONOI_FREQUENCY, 0.0, 16.0));
                }

                return this.parameterList;
            }
        }

        public void EnableDistance()
        {
            this.enableDistance = true;
        }

        public void EnableDistance(bool enable)
        {
            this.enableDistance = enable;
        }

        public double GetDisplacement()
        {
            return this.displacement;
        }

        public double GetFrequency()
        {
            return this.frequency;
        }

        public int GetSeed()
        {
            return this.seed;
        }

        public bool IsDistanceEnabled()
        {
            return this.enableDistance;
        }

        public override double GetValue(double x, double y, double z, double lowerXBound, double upperXBound, double lowerZBound, double upperZBound)
        {
            // This method could be more efficient by caching the seed values.  Fix
            // later.

            x *= this.frequency;
            y *= this.frequency;
            z *= this.frequency;

            int intX = x > 0.0 ? (int)x : (int)x - 1;
            int intY = y > 0.0 ? (int)y : (int)y - 1;
            int intZ = z > 0.0 ? (int)z : (int)z - 1;

            double minDist = 2147483647.0;
            double candidateX = 0;
            double candidateY = 0;
            double candidateZ = 0;

            // Inside each unit cube, there is a seed point at a random position.  Go
            // through each of the nearby cubes until we find a cube with a seed point
            // that is closest to the specified position.
            for (int curZ = intZ - 2; curZ <= intZ + 2; curZ++)
            {
                for (int curY = intY - 2; curY <= intY + 2; curY++)
                {
                    for (int curX = intX - 2; curX <= intX + 2; curX++)
                    {

                        // Calculate the position and distance to the seed point inside of
                        // this unit cube.
                        double posX = curX + noisegen.ValueNoise3D(curX, curY, curZ, this.seed);
                        double posY = curY + noisegen.ValueNoise3D(curX, curY, curZ, this.seed + 1);
                        double posZ = curZ + noisegen.ValueNoise3D(curX, curY, curZ, this.seed + 2);
                        double distX = posX - x;
                        double distY = posY - y;
                        double distZ = posZ - z;
                        double dist = distX * distX + distY * distY + distZ * distZ;

                        if (dist < minDist)
                        {
                            // This seed point is closer to any others found so far, so record
                            // this seed point.
                            minDist = dist;
                            candidateX = posX;
                            candidateY = posY;
                            candidateZ = posZ;
                        }
                    }
                }
            }

            double value;
            if (this.enableDistance)
            {
                // Determine the distance to the nearest seed point.
                double distX = candidateX - x;
                double distY = candidateY - y;
                double distZ = candidateZ - z;
                value = Math.Sqrt(distX * distX + distY * distY + distZ * distZ) * Math.Sqrt(3) - 1.0; // TODO: precalculate Sqrt(3) as constant
            }
            else
            {
                value = 0.0;
            }

            // Return the calculated distance with the displacement value applied.
            return value + (this.displacement * (double)noisegen.ValueNoise3D((int)Math.Floor(candidateX), (int)Math.Floor(candidateY), (int)Math.Floor(candidateZ)));
        }

        public void SetDisplacement(double displacement)
        {
            this.displacement = displacement;
        }

        public void SetFrequency(double frequency)
        {
            this.frequency = frequency;
        }

        public void SetSeed(int seed)
        {
            this.seed = seed;
        }

        public override void SetParameter(string name, object value)
        {
            switch (name.ToLower())
            {
                case "seed":
                    this.SetSeed(Convert.ToInt32(value));
                    break;
                case "displacement":
                    this.SetDisplacement(Convert.ToDouble(value));
                    break;
                case "frequency":
                    this.SetFrequency(Convert.ToDouble(value));
                    break;
            }
        }

        public override object GetParameter(string name)
        {
            switch (name.ToLower())
            {
                case "seed": return this.GetSeed();
                case "displacement": return this.GetDisplacement();
                case "frequency": return this.GetFrequency();
            }

            return null;
        }
    }
}

